/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
**     of its contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

//
// This file was originally named documenthandler.cpp
// It will likely be expanded to deal with SPIN files.
//

#include "documentmanager.h"

#include <QtGui/QTextDocument>
#include <QtGui/QTextCursor>
#include <QtGui/QFontDatabase>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtWidgets/QApplication>

extern int openspin_main(int argc, char *argv[], FILE* sf);

DocumentManager::DocumentManager()
    : m_target(0)
    , m_doc(0)
    , m_cursorPosition(-1)
    , m_selectionStart(0)
    , m_selectionEnd(0)
{
    spinfile = "";
    library = "";
    compstatus = "";
    copydone = false;
    initialize();
}

void DocumentManager::setTarget(QQuickItem *target)
{
    m_doc = 0;
    m_target = target;
    if (!m_target)
        return;

    QVariant doc = m_target->property("textDocument");
    if (doc.canConvert<QQuickTextDocument*>()) {
        QQuickTextDocument *qqdoc = doc.value<QQuickTextDocument*>();
        if (qqdoc)
            m_doc = qqdoc->textDocument();
    }
    emit targetChanged();
}

void DocumentManager::setFileUrl(const QUrl &arg)
{
    m_fileUrl = arg;
    QString fileName = QQmlFile::urlToLocalFileOrQrc(arg);
    if (QFile::exists(fileName)) {
        QFile file(fileName);
        if (file.open(QFile::ReadOnly)) {
            QByteArray data = file.readAll();
            QTextCodec *codec = QTextCodec::codecForHtml(data);
            setText(codec->toUnicode(data));
            if (m_doc)
                m_doc->setModified(false);
            if (fileName.isEmpty())
                m_documentTitle = QStringLiteral("untitled.txt");
            else
                m_documentTitle = QFileInfo(fileName).fileName();

            emit textChanged();
            emit documentTitleChanged();

            reset();
            setSpinFile(arg.toString());
            setUserPath(shortFileName(arg.toString()));
            emit fileUrlChanged();
        }
    }
}

void DocumentManager::saveFileUrl(const QUrl &arg)
{
    QByteArray textarray = text().toUtf8();
    m_fileUrl = arg;
    QString fileName = QQmlFile::urlToLocalFileOrQrc(arg);
    if (fileName.length() > 0) {
        QFile file(fileName);
        if (file.open(QFile::WriteOnly)) {
            file.write(textarray.data(), textarray.length());
            if (m_doc) {
                m_doc->setModified(false);
            }
        }
        setSpinFile(arg.toString());
        setUserPath(shortFileName(arg.toString()));
        emit fileUrlChanged();
    }
}

QUrl DocumentManager::spinFileUrl() const
{
    return m_spinFileUrl;
}

void DocumentManager::saveSpinFileUrl(QUrl arg)
{
    QString s = QQmlFile::urlToLocalFileOrQrc(arg);
    s = s.trimmed();
    if(s.endsWith("."))
        s = s.left(s.length()-1);
    if(!s.endsWith(".spin"))
        s += ".spin";
    arg = QUrl::fromLocalFile(s);
    if (m_spinFileUrl != arg) {
        m_spinFileUrl = arg;
        emit spinFileUrlChanged(arg);
    }
    saveFileUrl(arg);
}

QString DocumentManager::documentTitle() const
{
    return m_documentTitle;
}

void DocumentManager::setDocumentTitle(QString arg)
{
    if (m_documentTitle != arg) {
        m_documentTitle = arg;
        emit documentTitleChanged();
    }
}

void DocumentManager::setText(const QString &arg)
{
    if (m_text != arg) {
        m_text = arg;
        emit textChanged();
    }
}

QUrl DocumentManager::fileUrl() const
{
    return m_fileUrl;
}

QString DocumentManager::text() const
{
    return m_text;
}

void DocumentManager::setCursorPosition(int position)
{
    if (position == m_cursorPosition)
        return;

    m_cursorPosition = position;

    reset();
}

void DocumentManager::reset()
{
    emit fontFamilyChanged();
    emit alignmentChanged();
    emit boldChanged();
    emit italicChanged();
    emit underlineChanged();
    emit fontSizeChanged();
    emit textColorChanged();
}

QTextCursor DocumentManager::textCursor() const
{
    QTextCursor cursor = QTextCursor(m_doc);
    if (m_selectionStart != m_selectionEnd) {
        cursor.setPosition(m_selectionStart);
        cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor);
    } else {
        cursor.setPosition(m_cursorPosition);
    }
    return cursor;
}

void DocumentManager::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
    QTextCursor cursor = textCursor();
    if (!cursor.hasSelection())
        cursor.select(QTextCursor::WordUnderCursor);
    cursor.mergeCharFormat(format);
}

void DocumentManager::setSelectionStart(int position)
{
    m_selectionStart = position;
}

void DocumentManager::setSelectionEnd(int position)
{
    m_selectionEnd = position;
}

void DocumentManager::setAlignment(Qt::Alignment a)
{
    QTextBlockFormat fmt;
    fmt.setAlignment((Qt::Alignment) a);
    QTextCursor cursor = QTextCursor(m_doc);
    cursor.setPosition(m_selectionStart, QTextCursor::MoveAnchor);
    cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor);
    cursor.mergeBlockFormat(fmt);
    emit alignmentChanged();
}

Qt::Alignment DocumentManager::alignment() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return Qt::AlignLeft;
    return textCursor().blockFormat().alignment();
}

bool DocumentManager::bold() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return false;
    return textCursor().charFormat().fontWeight() == QFont::Bold;
}

bool DocumentManager::italic() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return false;
    return textCursor().charFormat().fontItalic();
}

bool DocumentManager::underline() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return false;
    return textCursor().charFormat().fontUnderline();
}

void DocumentManager::setBold(bool arg)
{
    QTextCharFormat fmt;
    fmt.setFontWeight(arg ? QFont::Bold : QFont::Normal);
    mergeFormatOnWordOrSelection(fmt);
    emit boldChanged();
}

void DocumentManager::setItalic(bool arg)
{
    QTextCharFormat fmt;
    fmt.setFontItalic(arg);
    mergeFormatOnWordOrSelection(fmt);
    emit italicChanged();
}

void DocumentManager::setUnderline(bool arg)
{
    QTextCharFormat fmt;
    fmt.setFontUnderline(arg);
    mergeFormatOnWordOrSelection(fmt);
    emit underlineChanged();
}

int DocumentManager::fontSize() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return 0;
    QTextCharFormat format = cursor.charFormat();
    return format.font().pointSize();
}

void DocumentManager::setFontSize(int arg)
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return;
    QTextCharFormat format;
    format.setFontPointSize(arg);
    mergeFormatOnWordOrSelection(format);
    emit fontSizeChanged();
}

QColor DocumentManager::textColor() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return QColor(Qt::black);
    QTextCharFormat format = cursor.charFormat();
    return format.foreground().color();
}

void DocumentManager::setTextColor(const QColor &c)
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return;
    QTextCharFormat format;
    format.setForeground(QBrush(c));
    mergeFormatOnWordOrSelection(format);
    emit textColorChanged();
}

QString DocumentManager::fontFamily() const
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return QString();
    QTextCharFormat format = cursor.charFormat();
    return format.font().family();
}

void DocumentManager::setFontFamily(const QString &arg)
{
    QTextCursor cursor = textCursor();
    if (cursor.isNull())
        return;
    QTextCharFormat format;
    format.setFontFamily(arg);
    mergeFormatOnWordOrSelection(format);
    emit fontFamilyChanged();
}

QStringList DocumentManager::defaultFontSizes() const
{
    // uhm... this is quite ugly
    QStringList sizes;
    QFontDatabase db;
    foreach (int size, db.standardSizes())
        sizes.append(QString::number(size));
    return sizes;
}


void DocumentManager::setLibrary(QString lib)
{
    library = lib;
}

QString DocumentManager::getLibrary()
{
    return library;
}

void DocumentManager::setUserPath(QString path)
{
    userpath = path;
}

QString DocumentManager::getUserPath()
{
    return userpath;
}

QString DocumentManager::shortFileName(QString fileName)
{
    QString rets;
    if(fileName.indexOf("/") > -1)
        rets = fileName.mid(fileName.lastIndexOf("/")+1);
    else
        rets = fileName;
    return rets;
}

QString DocumentManager::filePathName(QString fileName)
{
    QString rets;
    if(fileName.lastIndexOf("/") > -1)
        rets = fileName.mid(0,fileName.lastIndexOf("/")+1);
    else
        rets = fileName;
    return rets;
}

QString DocumentManager::spinFile()
{
    return spinfile;
}

void DocumentManager::setSpinFile(QString file)
{
    spinfile = file;
}

QString DocumentManager::status()
{
    return compstatus;
}

void DocumentManager::setMyCodePath(QString path)
{
    codepath = path;
}

QString DocumentManager::myCodePath()
{
    return codepath;
}

// dummy
void DocumentManager::setCodeUrl(QUrl url)
{

}

QUrl DocumentManager::codeUrl()
{
    return QUrl::fromLocalFile(codepath);
}

int DocumentManager::initialize()
{
#if defined(ANDROID)
    //workspace = "/storage/sdcard0/Android/data/org.microcsource.annoide/"; // private storage ... MYORG = org.microcsource.code
    //workspace = "/data/data/org.microcsource.annoide/files/";
    workspace = QUrl::fromLocalFile("/storage/sdcard0/Music/").toString();
#elif defined(APPLEIOS)
    workspace = "";
#else
    workspace = QUrl::fromLocalFile(QDir::tempPath()+"/Annoide/").toString();
    //workspace = QUrl::fromLocalFile(QDir::homePath()+"/Documents/Annoide/").toString();
#endif

    setUserPath(workspace);
    setMyCodePath(workspace+"Spin/");
    setLibrary(workspace+"Spin/Library/");
    setSpinFile(myCodePath()+"Welcome.spin");

    QString ws = QQmlFile::urlToLocalFileOrQrc(QUrl(workspace));

    QDir wdir(ws);
    if(!wdir.exists()) {
        wdir.mkdir(ws);
    }
    QDir sdir(ws+"Spin/");
    if(!sdir.exists()) {
        sdir.mkdir(ws+"Spin/");
    }
    QDir pdir(myCodePath());
    if(!pdir.exists()) {
        pdir.mkdir(myCodePath());
    }

    Q_INIT_RESOURCE(qml);

    /* create library folders */
    QStringList spinList = resources("spin");
    foreach(QString s, spinList) {
        QDir dir(":/"+s);
        if(dir.entryList().count() > 0) {
            QString dirName = ws+"Spin/"+dir.dirName()+"/";
            if(!wdir.exists(dirName)) {
                wdir.mkdir(dirName);
                qDebug() << "MkDird" << dirName;
            }
        }
    }

    // copy all files to library
    foreach(QString s, spinList) {
        QDir dir(":/"+s);
        if(dir.entryList().count() > 0)
            continue;
        QFile file(":/"+s);
        bool readable = file.exists();
        //qDebug() << QString("%1 %2").arg(readable?"readable":"unknown").arg(s);

        QString fileName = ws+s;
        QFile tar(fileName);

        QFlags<QFile::Permission> perm = QFileDevice::WriteOther | QFileDevice::WriteOwner | QFileDevice::WriteGroup;
        perm |= QFileDevice::ReadOther | QFileDevice::ReadOwner | QFileDevice::ReadGroup;
        tar.setPermissions(perm);

        if(readable && !tar.exists()) {
            QString fileName = ws+s;
            // can't just copy a resource file since it's read-only
            //QFile::copy(file.fileName(), fileName);
            if(file.open(QFile::ReadOnly)) {
                QByteArray dat = file.readAll();
                if(tar.open(QFile::WriteOnly)) {
                    tar.write(dat.constData(), dat.length());
                    tar.close();
                }
                file.close();
            }
            qDebug() << "Copied" << fileName;
        }

    }

    QApplication::processEvents();

    QString welcomeFile(QQmlFile::urlToLocalFileOrQrc(spinFile()));
    QString startFile(QQmlFile::urlToLocalFileOrQrc(myCodePath()+"MyFile.spin"));
    QFile::remove(startFile);
    QFile::copy(welcomeFile, startFile);

    setSpinFile(myCodePath()+"MyFile.spin");
    setFileUrl(spinFile());

    copydone = true;
    emit initDone();

    return 0;
}

QStringList DocumentManager::resources(QString prefix)
{
    prefix = ":/"+prefix;
    QDir dir(prefix);
    QStringList list;
    foreach(QString file,dir.entryList()) {
        QString path = dir.dirName()+"/"+file;
        list.append(path);
        QDir sub(prefix+"/"+file);
        if(sub.entryList().count() > 0) {
            QString path2 = dir.dirName()+"/"+sub.dirName();
            QStringList lst2 = resources(path2);
            foreach(QString s, lst2) {
                list.append(dir.dirName()+"/"+s);
            }
        }
    }
    return list;
}

void DocumentManager::compile(QString file)
{
    int rc = 0;
    char *arglist[32];
    QStringList args;

    if(file.length() == 0) {
        compstatus = "Invalid File";
        return;
    }

    if(QQmlFile::urlToLocalFileOrQrc(file).length() > 0)
        file = QQmlFile::urlToLocalFileOrQrc(file);

    QString outs = file.left(file.lastIndexOf("."));
    if(outs.count() > 0) {
        outs += ".binary";
    }

    QString lib = getLibrary();
    lib = QQmlFile::urlToLocalFileOrQrc(QUrl(lib));

    args.append("openspin");
    args.append("-L");
    args.append(lib);
    args.append("-b");
    args.append("-o");
    args.append(outs);
    //args.append("-v");
    args.append(file);

    QString ws = QQmlFile::urlToLocalFileOrQrc(QUrl(workspace));
    QString comptext(ws+"Spin/CompilerLog.txt");
    FILE *sf = fopen(comptext.toLatin1().data(), "w");
    if(!sf) {
        sf = stdout;
    }

    // setup arguments
    for(int n = 0; n < args.count(); n++) {
        arglist[n] = (char*) new char[args[n].length()+1];
        strcpy(arglist[n],args[n].toLatin1().data());
    }
    // run program
    rc = openspin_main(args.count(), arglist, sf);

    // free arguments
    for(int n = 0; n < args.count(); n++) {
        delete arglist[n];
    }

    compstatus = "";

    if(sf != stdout) {
        char buff[256];
        fclose(sf);

        // if we have an error, fill in compstatus
        //
        sf = fopen(comptext.toLatin1().data(), "r");
        if(sf) {
            while(!feof(sf)) {
                fgets(buff,256,sf);
                if(rc) {
                    compstatus.append(QString("%1").arg(buff));
                }
            }
            fclose(sf);
        }
    }

}
